﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;
using HeapSpecSupport;
using SimpleIR;

namespace Evaluator.EvalState
{
	/// <summary>
	/// Structure that implements all the primitive numeric operations.
	/// </summary>
	internal sealed class ENumeric : EMemoryValue
	{
		private readonly long m_sint;
		private readonly ulong m_uint;
		private readonly double m_float;

		private static Dictionary<IRNumericType, ENumeric> s_defaultnumerics = new Dictionary<IRNumericType,ENumeric>();

		internal ENumeric(IRNumericType numtype, long sval, ulong uval, double dval)
			: base(numtype)
		{
			this.m_sint = sval;
			this.m_uint = uval;
			this.m_float = dval;
		}

		internal ENumeric(IRSignedIntegerType numtype, bool bval)
			: base(numtype)
		{
			this.m_sint = (bval ? 1 : 0);
			this.m_uint = 0;
			this.m_float = 0.0;
		}

		internal ENumeric(IRSignedIntegerType numtype, long sval)
			: base(numtype)
		{
			this.m_sint = sval;
			this.m_uint = 0;
			this.m_float = 0.0;
		}

		internal ENumeric(IRUnSignedIntegerType numtype, ulong uval)
			: base(numtype)
		{
			this.m_sint = 0;
			this.m_uint = uval;
			this.m_float = 0.0;
		}

		internal ENumeric(IRFloatingPointType numtype, double dval)
			: base(numtype)
		{
			this.m_sint = 0;
			this.m_uint = 0;
			this.m_float = dval;
		}

		public override string ToString()
		{
			if(this.m_valuetype is IRSignedIntegerType)
				return this.m_sint.ToString();
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return this.m_uint.ToString();
			else
				return this.m_float.ToString();
		}

		public long SignedNumericValue
		{
			get
			{
				Contract.Requires(this.m_valuetype is IRSignedIntegerType);

				return this.m_sint;
			}
		}

		public ulong UnSignedNumericValue
		{
			get
			{
				Contract.Requires(this.m_valuetype is IRUnSignedIntegerType);

				return this.m_uint;
			}
		}

		public double FloatNumericValue
		{
			get
			{
				Contract.Requires(this.m_valuetype is IRFloatingPointType);

				return this.m_float;
			}
		}

		internal static ENumeric CreateDefaultValueForType(IRNumericType primty)
		{
			Contract.Requires(primty != null);

			if(!s_defaultnumerics.ContainsKey(primty))
				s_defaultnumerics.Add(primty, new ENumeric(primty, 0, 0, 0.0));

			return s_defaultnumerics[primty];
		}

		internal IRNumericType TypeOfNumeric
		{ get { return (IRNumericType)this.m_valuetype; } }

		/// <summary>
		/// Return true if the value is (must be) 0
		/// </summary>
		internal bool IsValueZero
		{ get { return this.m_sint == 0; } }

		/// <summary>
		/// Return true if the value is (must be) 1
		/// </summary>
		internal bool IsValueOne
		{ get { return this.m_sint == 1; } }

		//arith
		internal ENumeric Add(ENumeric op)
		{
			if(this.m_valuetype != op.m_valuetype)
			{
				DebugReport.ReportError("Type mismatch in add.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint + op.m_sint);
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, this.m_uint + op.m_uint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.Float64Type, this.m_float + op.m_float);
		}

		internal ENumeric Div(ENumeric op)
		{
			if(this.m_valuetype != op.m_valuetype)
			{
				DebugReport.ReportError("Type mismatch in div.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint / op.m_sint);
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, this.m_uint / op.m_uint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.Float64Type, this.m_float / op.m_float);
		}

		internal ENumeric Mult(ENumeric op)
		{
			if(this.m_valuetype != op.m_valuetype)
			{
				DebugReport.ReportError("Type mismatch in mult.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint * op.m_sint);
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, this.m_uint * op.m_uint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.Float64Type, this.m_float * op.m_float);
		}

		internal ENumeric Neg()
		{
			if(this.m_valuetype is IRUnSignedIntegerType)
			{
				DebugReport.ReportError("Cannot negate unsigned.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, -this.m_sint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.Float64Type, -this.m_float);
		}

		internal ENumeric Rem(ENumeric op)
		{
			if((this.m_valuetype != op.m_valuetype) || (this.m_valuetype is IRFloatingPointType))
			{
				DebugReport.ReportError("Type mismatch in rem.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint % op.m_sint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, this.m_uint % op.m_uint);
		}

		internal ENumeric Sub(ENumeric op)
		{
			if(this.m_valuetype != op.m_valuetype)
			{
				DebugReport.ReportError("Type mismatch in sub.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint - op.m_sint);
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, this.m_uint - op.m_uint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.Float64Type, this.m_float - op.m_float);
		}

		//bitwise
		internal ENumeric And(ENumeric op)
		{
			if((this.m_valuetype != op.m_valuetype) || (this.m_valuetype is IRFloatingPointType))
			{
				DebugReport.ReportError("Type mismatch in and.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint & op.m_sint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, this.m_uint & op.m_uint);
		}

		internal ENumeric Or(ENumeric op)
		{
			if((this.m_valuetype != op.m_valuetype) || (this.m_valuetype is IRFloatingPointType))
			{
				DebugReport.ReportError("Type mismatch in or.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint | op.m_sint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, this.m_uint | op.m_uint);
		}

		internal ENumeric Not()
		{
			if(this.m_valuetype is IRFloatingPointType)
			{
				DebugReport.ReportError("Cannot not floating points.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, ~this.m_sint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, ~this.m_uint);
		}

		internal ENumeric Shl(ENumeric op)
		{
			if((!(op.m_valuetype is IRSignedIntegerType)) || (op.m_sint > 64))
			{
				DebugReport.ReportError("Type mismatch in shl.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint << (int)op.m_sint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, this.m_uint << (int)op.m_sint);
		}

		internal ENumeric Shr(ENumeric op)
		{
			if((!(op.m_valuetype is IRSignedIntegerType)) || (op.m_sint > 64))
			{
				DebugReport.ReportError("Type mismatch in shr.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint >> (int)op.m_sint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, this.m_uint >> (int)op.m_sint);
		}

		internal ENumeric Xor(ENumeric op)
		{
			if((this.m_valuetype != op.m_valuetype) || (this.m_valuetype is IRFloatingPointType))
			{
				DebugReport.ReportError("Type mismatch in xor.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint ^ op.m_sint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.UInt64Type, this.m_uint ^ op.m_uint);
		}

		//compare
		internal ENumeric Eq(ENumeric op)
		{
			if(this.m_valuetype != op.m_valuetype)
			{
				DebugReport.ReportError("Type mismatch in eq.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint == op.m_sint);
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_uint == op.m_uint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_float == op.m_float);
		}

		internal ENumeric Lt(ENumeric op)
		{
			if(this.m_valuetype != op.m_valuetype)
			{
				DebugReport.ReportError("Type mismatch in lt.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint < op.m_sint);
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_uint < op.m_uint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_float < op.m_float);
		}

		internal ENumeric LtEq(ENumeric op)
		{
			if(this.m_valuetype != op.m_valuetype)
			{
				DebugReport.ReportError("Type mismatch in lteq.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint <= op.m_sint);
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_uint <= op.m_uint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_float <= op.m_float);
		}

		internal ENumeric Gt(ENumeric op)
		{
			if(this.m_valuetype != op.m_valuetype)
			{
				DebugReport.ReportError("Type mismatch in gt.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint > op.m_sint);
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_uint > op.m_uint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_float > op.m_float);
		}

		internal ENumeric GtEq(ENumeric op)
		{
			if(this.m_valuetype != op.m_valuetype)
			{
				DebugReport.ReportError("Type mismatch in gteq.");
				throw new InvalidOperationException();
			}

			if(this.m_valuetype is IRSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_sint >= op.m_sint);
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_uint >= op.m_uint);
			else
				return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_float >= op.m_float);
		}

		internal ENumeric ConvertTo(IRNumericType pt)
		{
			if(pt is IRSignedIntegerType)
				return new ENumeric((IRSignedIntegerType)pt, this.ConvertToInt((IRSignedIntegerType)pt));
			else if(pt is IRUnSignedIntegerType)
				return new ENumeric((IRUnSignedIntegerType)pt, this.ConvertToUint((IRUnSignedIntegerType)pt));
			else if(pt is IRFloatingPointType)
				return new ENumeric((IRFloatingPointType)pt, this.ConvertToFloat((IRFloatingPointType)pt));
			else
			{
				DebugReport.ReportError("Bad Convert.");
				throw new InvalidOperationException();
			}
		}

		//convert
		internal double ConvertToFloat(IRFloatingPointType pt)
		{
			if(this.m_valuetype is IRSignedIntegerType)
				return (double)this.m_sint;
			else if(this.m_valuetype is IRUnSignedIntegerType)
				return (double)this.m_uint;
			else
			{
				if(pt.FloatingPointTag == IRFloatingPointType.FloatingPointTypeTag.SingleTag)
				{
					if((this.m_float < Single.MinValue) | (Single.MaxValue < this.m_float))
						DebugReport.ReportError("Out of range conversion.");
					return this.m_float;
				}
				else
					return this.m_float;
			}
		}

		internal long ConvertToInt(IRSignedIntegerType pt)
		{
			if(this.m_valuetype is IRSignedIntegerType)
			{
				switch(pt.SignedTag)
				{
					case IRSignedIntegerType.SignedTypeTag.BoolTag:
						if((this.m_sint < 0) | (1 < this.m_sint))
							DebugReport.ReportError("Out of range conversion.");
						return this.m_sint;
					case IRSignedIntegerType.SignedTypeTag.SByteTag:
						if((this.m_sint < SByte.MinValue) | (SByte.MaxValue < this.m_sint))
							DebugReport.ReportError("Out of range conversion.");
						return this.m_sint;
					case IRSignedIntegerType.SignedTypeTag.Int16Tag:
						if((this.m_sint < Int16.MinValue) | (Int16.MaxValue < this.m_sint))
							DebugReport.ReportError("Out of range conversion.");
						return this.m_sint;
					case IRSignedIntegerType.SignedTypeTag.Int32Tag:
						if((this.m_sint < Int32.MinValue) | (Int32.MaxValue < this.m_sint))
							DebugReport.ReportError("Out of range conversion.");
						return this.m_sint;
					case IRSignedIntegerType.SignedTypeTag.Int64Tag:
						return this.m_sint;
					default:
						DebugReport.ReportError("Bad signed int convert.");
						throw new InvalidOperationException();
				}
			}
			else if(this.m_valuetype is IRUnSignedIntegerType)
			{
				switch(pt.SignedTag)
				{
					case IRSignedIntegerType.SignedTypeTag.BoolTag:
						if(1 < this.m_uint)
							DebugReport.ReportError("Out of range conversion.");
						return (long)this.m_uint;
					case IRSignedIntegerType.SignedTypeTag.SByteTag:
						if((ulong)SByte.MaxValue < this.m_uint)
							DebugReport.ReportError("Out of range conversion.");
						return (long)this.m_uint;
					case IRSignedIntegerType.SignedTypeTag.Int16Tag:
						if((ulong)Int16.MaxValue < this.m_uint)
							DebugReport.ReportError("Out of range conversion.");
						return (long)this.m_uint;
					case IRSignedIntegerType.SignedTypeTag.Int32Tag:
						if(Int32.MaxValue < this.m_uint)
							DebugReport.ReportError("Out of range conversion.");
						return (long)this.m_uint;
					case IRSignedIntegerType.SignedTypeTag.Int64Tag:
						if(Int64.MaxValue < this.m_uint)
							DebugReport.ReportError("Out of range conversion.");
						return (long)this.m_uint;
					default:
						DebugReport.ReportError("Bad signed int convert.");
						throw new InvalidOperationException();
				}
			}
			else
			{
				switch(pt.SignedTag)
				{
					case IRSignedIntegerType.SignedTypeTag.SByteTag:
						if((this.m_float < SByte.MinValue) | (SByte.MaxValue < this.m_float))
							DebugReport.ReportError("Out of range conversion.");
						return (long)this.m_float;
					case IRSignedIntegerType.SignedTypeTag.Int16Tag:
						if((this.m_float < Int16.MinValue) | (Int16.MaxValue < this.m_float))
							DebugReport.ReportError("Out of range conversion.");
						return (long)this.m_float;
					case IRSignedIntegerType.SignedTypeTag.Int32Tag:
						if((this.m_float < Int32.MinValue) | (Int32.MaxValue < this.m_float))
							DebugReport.ReportError("Out of range conversion.");
						return (long)this.m_float;
					case IRSignedIntegerType.SignedTypeTag.Int64Tag:
						if((this.m_float < Int64.MinValue) | (Int64.MaxValue < this.m_float))
							DebugReport.ReportError("Out of range conversion.");
						return (long)this.m_float;
					default:
						DebugReport.ReportError("Bad signed int convert.");
						throw new InvalidOperationException();
				}
			}
		}

		internal ulong ConvertToUint(IRUnSignedIntegerType pt)
		{
			if(this.m_valuetype is IRSignedIntegerType)
			{
				switch(pt.UnSignedTag)
				{
					case IRUnSignedIntegerType.UnsignedTypeTag.CharTag:
						if((this.m_sint < 0) | (Char.MaxValue < this.m_sint))
							DebugReport.ReportError("Out of range conversion.");
						return (ulong)this.m_sint;
					case IRUnSignedIntegerType.UnsignedTypeTag.ByteTag:
						if((this.m_sint < 0) | (Byte.MaxValue < this.m_sint))
							DebugReport.ReportError("Out of range conversion.");
						return (ulong)this.m_sint;
					case IRUnSignedIntegerType.UnsignedTypeTag.UInt16Tag:
						if((this.m_sint < 0) | (UInt16.MaxValue < this.m_sint))
							DebugReport.ReportError("Out of range conversion.");
						return (ulong)this.m_sint;
					case IRUnSignedIntegerType.UnsignedTypeTag.UInt32Tag:
						if((this.m_sint < 0) | (UInt32.MaxValue < this.m_sint))
							DebugReport.ReportError("Out of range conversion.");
						return (ulong)this.m_sint;
					case IRUnSignedIntegerType.UnsignedTypeTag.UInt64Tag:
						if(this.m_sint < 0)
							DebugReport.ReportError("Out of range conversion.");
						return (ulong)this.m_sint;
					default:
						DebugReport.ReportError("Bad unsigned int convert.");
						throw new InvalidOperationException();
				}
			}
			else if(this.m_valuetype is IRUnSignedIntegerType)
			{
				switch(pt.UnSignedTag)
				{
					case IRUnSignedIntegerType.UnsignedTypeTag.CharTag:
						if(Char.MaxValue < this.m_uint)
							DebugReport.ReportError("Out of range conversion.");
						return this.m_uint;
					case IRUnSignedIntegerType.UnsignedTypeTag.ByteTag:
						if(Byte.MaxValue < this.m_uint)
							DebugReport.ReportError("Out of range conversion.");
						return this.m_uint;
					case IRUnSignedIntegerType.UnsignedTypeTag.UInt16Tag:
						if(UInt16.MaxValue < this.m_uint)
							DebugReport.ReportError("Out of range conversion.");
						return this.m_uint;
					case IRUnSignedIntegerType.UnsignedTypeTag.UInt32Tag:
						if(UInt32.MaxValue < this.m_uint)
							DebugReport.ReportError("Out of range conversion.");
						return this.m_uint;
					case IRUnSignedIntegerType.UnsignedTypeTag.UInt64Tag:
						return this.m_uint;
					default:
						DebugReport.ReportError("Bad unsigned int convert.");
						throw new InvalidOperationException();
				}
			}
			else
			{
				switch(pt.UnSignedTag)
				{
					case IRUnSignedIntegerType.UnsignedTypeTag.CharTag:
						if((this.m_float < 0) | (Char.MaxValue < this.m_float))
							DebugReport.ReportError("Out of range conversion.");
						return (ulong)this.m_float;
					case IRUnSignedIntegerType.UnsignedTypeTag.ByteTag:
						if((this.m_float < 0) | (Byte.MaxValue < this.m_float))
							DebugReport.ReportError("Out of range conversion.");
						return (ulong)this.m_float;
					case IRUnSignedIntegerType.UnsignedTypeTag.UInt16Tag:
						if((this.m_float < 0) | (UInt16.MaxValue < this.m_float))
							DebugReport.ReportError("Out of range conversion.");
						return (ulong)this.m_float;
					case IRUnSignedIntegerType.UnsignedTypeTag.UInt32Tag:
						if((this.m_float < 0) | (UInt32.MaxValue < this.m_float))
							DebugReport.ReportError("Out of range conversion.");
						return (ulong)this.m_float;
					case IRUnSignedIntegerType.UnsignedTypeTag.UInt64Tag:
						if((this.m_float < 0) | (UInt64.MaxValue < this.m_float))
							DebugReport.ReportError("Out of range conversion.");
						return (ulong)this.m_float;
					default:
						DebugReport.ReportError("Bad unsigned int convert.");
						throw new InvalidOperationException();
				}
			}
		}
	}
}
